Skip to content

feat: echo the active retention windows in the dashboard#499

Merged
pratyush618 merged 10 commits into
masterfrom
feat/dashboard-retention-echo
Jul 22, 2026
Merged

feat: echo the active retention windows in the dashboard#499
pratyush618 merged 10 commits into
masterfrom
feat/dashboard-retention-echo

Conversation

@kartikeya-27

@kartikeya-27 kartikeya-27 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Closes #489.

The dashboard never mentioned retention, so rows silently vanished from its
listings once a window elapsed — with retention on by default since #448, every
operator meets that blind.

Why the dashboard can't just read its own config

Retention is SchedulerConfig.retention, resolved by effective_retention()
inside the worker process. Python carries it on the Queue constructor;
the Node and Java shells carry it as a worker option the dashboard process
never sees. A dashboard computing the windows locally would print the
recommended defaults while a worker elsewhere ran with retention disabled.

So the cleaner publishes what it applies

auto_cleanup — the elected retention leader, whose config actually governs the
deletes — writes its resolved windows to retention:effective:<namespace>
before each sweep, and every dashboard echoes that document.

{ "enabled": true, "defaulted": true, "namespace": "default",
  "reported_at": 1753200000000,
  "windows": { "task_logs_ttl_ms": 259200000, "archived_jobs_ttl_ms": 604800000,
               "job_errors_ttl_ms": 604800000, "task_metrics_ttl_ms": 604800000,
               "dead_letter_ttl_ms": 2592000000 } }

Three states stay distinct at every layer: unreported (no worker has swept —
the policy is unknown), enabled, and disabled. retention: joins the
reserved settings prefixes in all three shells: the document is a report, not a
knob, so it is neither listed nor writable through /api/settings.

Surface

  • GET /api/retention in all three shells
  • Queue.effective_retention() / aeffective_retention() (Python),
    queue.effectiveRetention() (Node), Taskito.effectiveRetention() (Java)
  • A Retention card on the dashboard Settings page: per-table windows, an on /
    off / not-reported badge, a warning when the recommended defaults are in
    force, and the reporting namespace and time
  • Contract documented in BINDING_CONTRACT.md; docs cover the endpoint and the
    Python accessor

The windows appear after the leader's first cleanup tick (cleanup_interval,
1200 ticks by default), so a fresh deployment shows the unreported state for a
minute or two. Publishing at startup was rejected: a non-leader's config does
not govern the deletes, and two differently-configured workers would flap the
document.

Verification

cargo test --workspace + clippy + --features postgres / redis checks ·
Python 1285 passed / 7 skipped (incl. a worker-driven publish end to end) ·
Node 469 vitest · Java :test incl. 5 new dashboard cases · dashboard
pnpm ci (biome, tsc, vitest, build) · docs build · both UI states checked in
a real browser against a live dashboard.

Summary by CodeRabbit

  • New Features
    • Added effective retention reporting across Python, Node.js, and Java SDKs, including per-table retention windows plus “reported/enabled/defaulted” status.
    • Added read-only GET /api/retention to the dashboard and a new Settings UI section to display the latest worker-reported policy.
  • Documentation
    • Documented effective retention resolution, the reserved retention policy key, and window semantics (milliseconds, “keep forever”, and unreported vs disabled).
  • Bug Fixes
    • Ensured worker-published retention data is protected from generic settings listing, reads, writes, and deletes.
  • Tests
    • Added retention publication and dashboard endpoint test coverage.

The windows live in the worker's SchedulerConfig, so a dashboard in another
process cannot see them. The elected cleaner now records what it applies
under retention:effective:<namespace> before each sweep.
Adds Queue.effective_retention() and GET /api/retention. The retention:
prefix joins the reserved settings namespaces — the published policy is a
report, not a knob.
Adds queue.effectiveRetention() and GET /api/retention. The retention:
prefix joins the reserved settings namespaces — the published policy is a
report, not a knob.
Adds Taskito.effectiveRetention() and GET /api/retention. The retention:
prefix joins the reserved settings namespaces — the published policy is a
report, not a knob.
Settings now explains why rows leave the listings: the per-table windows the
retention leader reported, with distinct states for on, off, and unreported.
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 40388518-dba1-4c70-806b-7a39e1eb7676

📥 Commits

Reviewing files that changed from the base of the PR and between 6a3b6ca and 0988947.

📒 Files selected for processing (4)
  • dashboard/src/features/settings/components/retention-section.tsx
  • dashboard/src/features/settings/derived.test.ts
  • dashboard/src/features/settings/derived.ts
  • dashboard/src/routes/settings.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • dashboard/src/features/settings/derived.test.ts
  • dashboard/src/features/settings/components/retention-section.tsx
  • dashboard/src/features/settings/derived.ts

📝 Walkthrough

Walkthrough

Workers publish namespaced effective-retention snapshots. Java, Node, and Python SDKs expose them, dashboard APIs return them, reserved settings keys are protected, and the settings page displays retention status and windows.

Changes

Core publication and SDK access

Layer / File(s) Summary
Core retention publication
crates/taskito-core/...
Defines the effective-retention document, storage helpers, namespace handling, and leader-only publication before cleanup sweeps.
SDK retention inspection
crates/taskito-*/..., sdks/java/..., sdks/node/..., sdks/python/...
Adds native bindings, typed models, parsers, and queue inspection APIs for published retention snapshots.

Dashboard integration

Layer / File(s) Summary
Dashboard retention endpoints
sdks/java/.../dashboard/..., sdks/node/.../dashboard/..., sdks/python/.../dashboard/..., docs/content/docs/...
Adds GET /api/retention, maps published snapshots into dashboard contracts, protects retention: settings, and documents the endpoint.
Settings retention display
dashboard/src/features/settings/..., dashboard/src/routes/settings.tsx
Fetches retention data, formats windows, renders reported/disabled/defaulted states, and adds the retention section to Settings.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CleanupWorker
  participant SharedStorage
  participant QueueSDK
  participant DashboardAPI
  participant SettingsPage
  CleanupWorker->>SharedStorage: publish effective retention JSON
  QueueSDK->>SharedStorage: read namespaced retention JSON
  QueueSDK-->>DashboardAPI: provide effective retention snapshot
  DashboardAPI-->>SettingsPage: return retention contract
  SettingsPage->>SettingsPage: render status and retention windows
Loading

Possibly related PRs

  • ByteVeda/taskito#441: Both changes modify the scheduler cleanup flow around retention publication and purge sweeps.
  • ByteVeda/taskito#443: Both changes build on the per-table retention implementation in the scheduler and retention modules.
  • ByteVeda/taskito#448: Both changes modify recommended/defaulted retention behavior.

Suggested labels: storage

Suggested reviewers: pratyush618

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.59% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: surfacing active retention windows in the dashboard.
Linked Issues check ✅ Passed The PR adds dashboard retention visibility with per-table windows and enabled status sourced from the worker-published policy, matching #489.
Out of Scope Changes check ✅ Passed All changed files support the retention-visibility feature or its docs/tests; no unrelated scope appears introduced.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dashboard-retention-echo

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pratyush618
pratyush618 requested review from pratyush618 and removed request for pratyush618 July 22, 2026 17:59
@kartikeya-27
kartikeya-27 requested a review from pratyush618 July 22, 2026 18:00
@kartikeya-27 kartikeya-27 self-assigned this Jul 22, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
crates/taskito-core/BINDING_CONTRACT.md (1)

238-241: 🔒 Security & Privacy | 🔵 Trivial

Reserved-prefix guarantee is enforced per-SDK, not centrally.

The contract states the retention: prefix must never be listed/written/deleted through generic KV endpoints, but nothing in the core Storage trait (e.g. set_setting) enforces this — it's each SDK's dashboard settings handler that must independently reject the prefix (as seen in the Java SettingsHandlers.PROTECTED_PREFIXES check). That's fine as an initial implementation, but it means a missed check in any one language's port silently reopens the spoofing hole this section is meant to close.

Worth tracking as a follow-up: consider a shared/central guard (e.g. in Storage::set_setting/delete_setting or a shared helper each binding calls) so the guarantee doesn't rely on N independent re-implementations staying in sync.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/taskito-core/BINDING_CONTRACT.md` around lines 238 - 241, The reserved
retention: prefix is currently protected only by individual SDK settings
handlers, leaving other bindings able to spoof the published policy. Add a
shared central guard or reusable helper used by generic KV list, write, and
delete paths, including Storage::set_setting and delete_setting where
applicable, so retention: is rejected consistently alongside auth: across all
SDKs.
crates/taskito-core/src/scheduler/retention.rs (1)

145-153: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Consider logging a swallowed parse failure.

.ok() here maps a genuine deserialization error to None with no trace. That's the correct external contract (unparseable → "unreported"), but during a schema change or mixed-version rollout this makes a real bug indistinguishable from "no leader has swept yet" — an operator would just see the dashboard report unreported with no signal of why.

♻️ Suggested tweak (keeps the same `Option` contract, adds a diagnostic)
     let Some(raw) = storage.get_setting(&retention_setting_key(namespace))? else {
         return Ok(None);
     };
-    Ok(serde_json::from_str(&raw).ok())
+    Ok(serde_json::from_str(&raw)
+        .inspect_err(|e| log::warn!("published retention document is unparseable: {e}"))
+        .ok())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/taskito-core/src/scheduler/retention.rs` around lines 145 - 153,
Update read_effective_retention to preserve the existing unparseable-to-None
contract while logging deserialization failures from serde_json::from_str.
Include the namespace or retention setting context and the parse error in the
diagnostic, without changing successful parsing or missing-setting behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dashboard/src/features/settings/derived.ts`:
- Around line 182-189: Update formatRetentionWindow so the positive-duration
fallback never formats as 0 seconds: clamp the rounded millisecond-to-second
value to at least one before passing it to plural. Add a test covering a
positive sub-second window such as 1–499ms and expect “1 second”.

In `@dashboard/src/routes/settings.tsx`:
- Around line 15-20: The /settings loader currently blocks on retention data and
couples retention failures to the Settings loading state. In
dashboard/src/routes/settings.tsx lines 15-20, keep awaiting settingsQuery() but
prefetch retentionQuery() best-effort without letting failure reject the loader;
in dashboard/src/features/settings/components/retention-section.tsx lines 70-89,
handle retention loading or error states independently so they do not reuse the
Settings loader/skeleton state.

---

Nitpick comments:
In `@crates/taskito-core/BINDING_CONTRACT.md`:
- Around line 238-241: The reserved retention: prefix is currently protected
only by individual SDK settings handlers, leaving other bindings able to spoof
the published policy. Add a shared central guard or reusable helper used by
generic KV list, write, and delete paths, including Storage::set_setting and
delete_setting where applicable, so retention: is rejected consistently
alongside auth: across all SDKs.

In `@crates/taskito-core/src/scheduler/retention.rs`:
- Around line 145-153: Update read_effective_retention to preserve the existing
unparseable-to-None contract while logging deserialization failures from
serde_json::from_str. Include the namespace or retention setting context and the
parse error in the diagnostic, without changing successful parsing or
missing-setting behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8d4ee120-86f6-4471-8794-0dfa3e4b552b

📥 Commits

Reviewing files that changed from the base of the PR and between 4c75684 and 6a3b6ca.

📒 Files selected for processing (49)
  • crates/taskito-core/BINDING_CONTRACT.md
  • crates/taskito-core/src/lib.rs
  • crates/taskito-core/src/scheduler/maintenance.rs
  • crates/taskito-core/src/scheduler/mod.rs
  • crates/taskito-core/src/scheduler/retention.rs
  • crates/taskito-java/src/queue/admin.rs
  • crates/taskito-node/src/queue/admin.rs
  • crates/taskito-python/src/py_queue/mod.rs
  • dashboard/src/features/settings/api.ts
  • dashboard/src/features/settings/components/retention-section.tsx
  • dashboard/src/features/settings/derived.test.ts
  • dashboard/src/features/settings/derived.ts
  • dashboard/src/features/settings/hooks.ts
  • dashboard/src/features/settings/index.ts
  • dashboard/src/features/settings/types.ts
  • dashboard/src/routes/settings.tsx
  • docs/content/docs/java/guides/operations/dashboard.mdx
  • docs/content/docs/node/guides/operations/dashboard-api.mdx
  • docs/content/docs/python/guides/dashboard/rest-api.mdx
  • docs/content/docs/python/guides/operations/job-management.mdx
  • sdks/java/src/main/java/org/byteveda/taskito/DefaultTaskito.java
  • sdks/java/src/main/java/org/byteveda/taskito/Taskito.java
  • sdks/java/src/main/java/org/byteveda/taskito/dashboard/DashboardServer.java
  • sdks/java/src/main/java/org/byteveda/taskito/dashboard/api/Contract.java
  • sdks/java/src/main/java/org/byteveda/taskito/dashboard/api/RetentionHandlers.java
  • sdks/java/src/main/java/org/byteveda/taskito/dashboard/api/SettingsHandlers.java
  • sdks/java/src/main/java/org/byteveda/taskito/internal/JniQueueBackend.java
  • sdks/java/src/main/java/org/byteveda/taskito/internal/NativeQueue.java
  • sdks/java/src/main/java/org/byteveda/taskito/model/EffectiveRetention.java
  • sdks/java/src/main/java/org/byteveda/taskito/spi/QueueBackend.java
  • sdks/java/src/test/java/org/byteveda/taskito/dashboard/DashboardRetentionTest.java
  • sdks/node/src/dashboard/handlers/index.ts
  • sdks/node/src/dashboard/handlers/retention.ts
  • sdks/node/src/dashboard/handlers/settings.ts
  • sdks/node/src/dashboard/routes.ts
  • sdks/node/src/index.ts
  • sdks/node/src/queue.ts
  • sdks/node/src/retention.ts
  • sdks/node/src/types.ts
  • sdks/node/test/dashboard/retention.test.ts
  • sdks/python/taskito/__init__.py
  • sdks/python/taskito/_taskito.pyi
  • sdks/python/taskito/async_support/mixins.py
  • sdks/python/taskito/dashboard/handlers/retention.py
  • sdks/python/taskito/dashboard/handlers/settings.py
  • sdks/python/taskito/dashboard/routes.py
  • sdks/python/taskito/mixins/inspection.py
  • sdks/python/taskito/retention.py
  • sdks/python/tests/dashboard/test_dashboard_retention.py

Comment thread dashboard/src/features/settings/derived.ts Outdated
Comment thread dashboard/src/routes/settings.tsx Outdated
The loader awaited GET /api/retention, so a backend that failed it replaced
the whole page — branding, integrations, links — with an error page.
Rounding 1-499ms to "0 seconds" read as the immediate purge it isn't.
@pratyush618
pratyush618 merged commit 950cefd into master Jul 22, 2026
58 of 60 checks passed
@pratyush618
pratyush618 deleted the feat/dashboard-retention-echo branch July 22, 2026 21:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Echo the active retention windows in the dashboard

2 participants